
#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <sdl/sdl.h>
#include <png.h>
#include <chunks/GuacImage_setup.h>
#include "guacimage.h"
#include "shared_setup.h"

int raw_draw(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    SDL_Surface* src = img->surf;
    SDL_Rect* srcrect = img->prect;
    SDL_Surface* dst = SDL_GetVideoSurface();
    SDL_Rect dstrect = {luaL_optint(L,2,0), luaL_optint(L,3,0), 0, 0};
    SDL_BlitSurface(src, srcrect, dst, &dstrect);
    return 0;
}

int raw_destroyimage(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (!(img->prect))
        SDL_FreeSurface(img->surf);
    lua_pushnil(L);
    lua_setmetatable(L,1);
    return 0;
}

int raw_imagesummary(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (img->prect)
        lua_pushfstring(L, "image window: %dx%d (%dbpp)", img->prect->w, img->prect->h, img->surf->format->BitsPerPixel);
    else
        lua_pushfstring(L, "image: %dx%d (%dbpp)", img->surf->w, img->surf->h, img->surf->format->BitsPerPixel);
    return 1;
}

int raw_imagegetwidth(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (img->prect)
        lua_pushinteger(L, img->prect->w);
    else
        lua_pushinteger(L, img->surf->w);
    return 1;
}

int raw_imagegetheight(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (img->prect)
        lua_pushinteger(L, img->prect->h);
    else
        lua_pushinteger(L, img->surf->h);
    return 1;
}

int raw_imagegetbpp(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    lua_pushinteger(L, img->surf->format->BitsPerPixel);
    return 1;
}

int raw_imagegettype(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (img->surf->format->BitsPerPixel == 8)
        lua_pushliteral(L, "paletted");
    else if (img->surf->format->Amask)
        lua_pushliteral(L, "rgba");
    else
        lua_pushliteral(L, "rgb");
    return 1;
}

static Uint32 GetPixel(SDL_Surface *surf, int x, int y)
{
	//This function returns pixels color
	int bpp = surf->format->BytesPerPixel;
	Uint8 *p = (Uint8 *)surf->pixels + y * surf->pitch + x * bpp;

	switch (bpp)
	{
		case 1:
			return *p;

		case 2:
			return *(Uint16 *)p;

		case 3:
			if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
				return p[0] << 16 | p[1] << 8 | p[2];
			else
				return p[0] | p[1] << 8 | p[2] << 16;

		case 4:
			return *(Uint32 *)p;

		default:
			return 0;
	}
}

static int raw_imagergba(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    Uint8 r,g,b,a;
    switch(lua_gettop(L)-1)
    {
        case 1:
            SDL_GetRGBA(luaL_checkint(L,2), img->surf->format, &r,&g,&b,&a);
            break;
        case 2:
            if (SDL_MUSTLOCK(img->surf))
                SDL_LockSurface(img->surf);
            int colcode = GetPixel(img->surf, luaL_checkint(L,2), luaL_checkint(L,3));
            if (SDL_MUSTLOCK(img->surf))
                SDL_UnlockSurface(img->surf);
            SDL_GetRGBA(colcode, img->surf->format, &r,&g,&b,&a);
            break;
        default:
            return luaL_error(L, "unexpected number of arguments (expecting 1 or 2, for %d)", lua_gettop(L)-1);
    }
    lua_pushinteger(L,r);
    lua_pushinteger(L,g);
    lua_pushinteger(L,b);
    lua_pushinteger(L,a);
    return 4;
}

static int raw_imagecolcode(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    int exact = 0;
    if (lua_isboolean(L,-1))
    {
        exact = lua_toboolean(L,-1);
        lua_pop(L,1);
    }
    switch(lua_gettop(L)-1)
    {
        case 2:
            if (SDL_MUSTLOCK(img->surf))
                SDL_LockSurface(img->surf);
            lua_pushinteger(L, GetPixel(img->surf, luaL_checkint(L,2), luaL_checkint(L,3)));
            if (SDL_MUSTLOCK(img->surf))
                SDL_UnlockSurface(img->surf);
            return 1;
        case 3:
            {
                Uint8 r = (Uint8)luaL_checkint(L,2);
                Uint8 g = (Uint8)luaL_checkint(L,3);
                Uint8 b = (Uint8)luaL_checkint(L,4);
                lua_pushinteger(L, SDL_MapRGB(img->surf->format, r,g,b));
                if (exact)
                {
                    Uint8 r2,g2,b2;
                    SDL_GetRGB(lua_tointeger(L,-1), img->surf->format, &r2,&g2,&b2);
                    if (r != r2 || g != g2 || b != b2)
                        lua_pushnil(L);
                }
                return 1;
            }
        case 4:
            {
                Uint8 r = (Uint8)luaL_checkint(L,2);
                Uint8 g = (Uint8)luaL_checkint(L,3);
                Uint8 b = (Uint8)luaL_checkint(L,4);
                Uint8 a = (Uint8)luaL_checkint(L,5);
                lua_pushinteger(L, SDL_MapRGBA(img->surf->format, r,g,b,a));
                if (exact)
                {
                    Uint8 r2,g2,b2,a2;
                    SDL_GetRGBA(lua_tointeger(L,-1), img->surf->format, &r2,&g2,&b2,&a2);
                    if (r != r2 || g != g2 || b != b2 || a != a2)
                        lua_pushnil(L);
                }
                return 1;
            }
        default:
            return luaL_error(L, "unexpected number of arguments (expecting 2, 3 or 4)");
    }
}

static int raw_image_getmask(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    SDL_Surface* surf = img->surf;

    if (!(surf->flags & SDL_SRCCOLORKEY))
        return 0;

    lua_pushinteger(L, surf->format->colorkey);
    return 1;
}

static int raw_image_setmask(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (img->flags & IMG_FLAG_LOCKED)
        return luaL_error(L, "attempt to modify read-only image");
    SDL_Surface* surf = img->surf;

    if (surf->format->Amask)
    {
        if (lua_isnil(L,2))
            return 0;
        return luaL_error(L, "mask cannot be set on an rgba image");
    }

    if (lua_isnoneornil(L,2))
        SDL_SetColorKey(surf, 0, 0);
    else if (!lua_isnumber(L,2))
        return luaL_error(L, "mask must be a colcode number value");
    else
        SDL_SetColorKey(surf, SDL_SRCCOLORKEY, lua_tointeger(L,2));
    return 0;
}

static int raw_image_window(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (img->prect)
        return luaL_error(L, "cannot window an already-windowed image");

    GuacImage* newWindow = (GuacImage*)lua_newuserdata(L,sizeof(GuacImage));
    newWindow->flags = (img->flags & IMG_FLAG_LOCKED);
    newWindow->surf = img->surf;
    newWindow->prect = NULL;
    newWindow->prect = &(newWindow->rect);
    newWindow->prect->x = luaL_checkint(L,2);
    newWindow->prect->y = luaL_checkint(L,3);
    newWindow->prect->w = luaL_checkint(L,4);
    newWindow->prect->h = luaL_checkint(L,5);
    newWindow->handle_x = newWindow->handle_y = 0;
    return 1;
}

enum image_types {paletted, rgb, rgba};
static const char* image_type_names[] = {"paletted", "rgb", "rgba", NULL};

static int raw_image_create(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_newuserdata(L, sizeof(GuacImage));
    img->flags = 0;
    img->handle_x = img->handle_y = 0;
    switch(luaL_checkoption(L,1,NULL,image_type_names))
    {
        case paletted:
            img->surf = SDL_CreateRGBSurface(SDL_SWSURFACE,
                luaL_checkint(L,2), luaL_checkint(L,3), 8, 0,0,0,0);
            break;
        case rgb:
            img->surf = SDL_CreateRGBSurface(SDL_SWSURFACE,
                luaL_checkint(L,2), luaL_checkint(L,3), 32,
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000
#else
                0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000
#endif
            );
            break;
        case rgba:
            img->surf = SDL_CreateRGBSurface(SDL_SWSURFACE,
                luaL_checkint(L,2), luaL_checkint(L,3), 32,
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
#else
                0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
#endif
            );
            break;
    }
    img->prect = NULL;
    return 1;
}

static int raw_image_clear(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L, 1);
    if (img->flags & IMG_FLAG_LOCKED)
        return luaL_error(L, "attempt to modify read-only image");
    SDL_SetClipRect(img->surf, img->prect);
    SDL_FillRect(img->surf, img->prect, luaL_optint(L,2,0));
    return 0;
}

static int raw_image_lock(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_newuserdata(L, sizeof(GuacImage));
    img->flags |= IMG_FLAG_LOCKED;
    return 0;
}

static int raw_image_gethandle(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_newuserdata(L, sizeof(GuacImage));
    lua_pushnumber(L, img->handle_x);
    lua_pushnumber(L, img->handle_y);
    return 2;
}

static int raw_image_sethandle(lua_State *L)
{
    GuacImage* img = (GuacImage*)lua_touserdata(L,1);
    if (img->flags & IMG_FLAG_LOCKED)
        return luaL_error(L, "attempt to modify read-only image");
    if (!lua_isnoneornil(L,2))
        img->handle_x = luaL_checknumber(L,2);
    if (!lua_isnoneornil(L,3))
        img->handle_y = luaL_checknumber(L,3);
    return 0;
}

static luaL_reg raw_image_functions[] = {
    {"loadpng", raw_loadpng},
    {"loadbmp", raw_loadbmp},
    {"loadjpeg", raw_loadjpeg},
    {"image_create", raw_image_create},
    {"image_clear", raw_image_clear},
    {"draw", raw_draw},
    {"destroyimage", raw_destroyimage},
    {"imagesummary", raw_imagesummary},
    {"setmask", raw_image_setmask},
    {"getmask", raw_image_getmask},
    {"window", raw_image_window},
    {"gethandle", raw_image_gethandle},
    {"sethandle", raw_image_sethandle},

    {"lock",raw_image_lock},

    {"getmetatable", raw_getmetatable},
    {"setmetatable", raw_setmetatable},
    {"getfenv", raw_getfenv},
    {"setfenv", raw_setfenv},

    {"imagegetwidth", raw_imagegetwidth},
    {"imagegetheight", raw_imagegetheight},
    {"imagegetbpp", raw_imagegetbpp},
    {"imagegettype", raw_imagegettype},
    {"imagergba", raw_imagergba},
    {"imagecolcode", raw_imagecolcode},
    {NULL, NULL}
};

int luaopen_image(lua_State* L)
{
    lua_newtable(L);
    luaL_register(L, NULL, raw_image_functions);
    lua_pushvalue(L, LUA_REGISTRYINDEX);
    lua_setfield(L,-2,"registry");

    luaL_loadbuffer(L, chunk_GuacImage_setup, sizeof(chunk_GuacImage_setup), "SYSTEM:GuacImage_setup.lua");
    lua_insert(L,-2);
    lua_call(L,1,1);

    lua_pushvalue(L,-1);
    lua_setglobal(L, "image");

    return 1;
}
